﻿using StackExchange.Redis;
using System.Globalization;

namespace _4_NovaAdmin.Web.Core.IM;

public class ImUtility
{
    public string[] _servers;
    public string _redisPrefix;
    public string _pathMatch;
    public ImOptions imOptions;
    public readonly StackExchange.Redis.IDatabase _redis;
    public StackExchange.Redis.ISubscriber _subscriber;

    /// <summary>
    /// 推送消息的事件，可审查推向哪个Server节点
    /// </summary>
    public EventHandler<ImSendEventArgs> OnSend;
    public ImUtility(RedisClient client, IOptions<ImOptions> options)
    {
        if (options.Value.Servers.Any() == false) throw new ArgumentException("ImClientOptions.Servers 参数不能为空");
        imOptions = options.Value;
        _servers = options.Value.Servers;
        _redisPrefix = $"wsim{options.Value.PathMatch.Replace('/', '_')}";
        _pathMatch = options.Value.PathMatch ?? "/ws";
        _redis = client.GetDatabase();
        _subscriber = client.GetSubscriber();
    }

    /// <summary>
    /// 负载分区规则：取clientId后四位字符，转成10进制数字0-65535，求模
    /// 实现一种简单的负载均衡，将不同的客户端分配到不同的服务器上
    /// </summary>
    /// <param name="clientId">客户端id</param>
    /// <returns></returns>
    public string SelectServer(Guid clientId)
    {
        var servers_idx = int.Parse(clientId.ToString("N").Substring(28), NumberStyles.HexNumber) % _servers.Length;
        if (servers_idx >= _servers.Length) servers_idx = 0;
        return _servers[servers_idx];
    }

    /// <summary>
    /// 向指定的多个客户端id发送消息
    /// </summary>
    /// <param name="senderClientId">发送者的客户端id</param>
    /// <param name="receiveClientId">接收者的客户端id</param>
    /// <param name="message">消息</param>
    /// <param name="receipt">是否回执</param>
    public void SendMessage(Guid senderClientId, IEnumerable<Guid> receiveClientId, object message, bool receipt = false)
    {
        // 去重接收者ID，确保每个接收者只接收一次消息
        receiveClientId = receiveClientId.Distinct().ToArray();
        // 初始化一个字典，用于存储每个服务器的消息发送参数
        Dictionary<string, ImSendEventArgs> redata = new Dictionary<string, ImSendEventArgs>();
        // 遍历所有接收者的ID
        foreach (var uid in receiveClientId)
        {
            // 根据接收者的ID选择一个服务器
            string server = SelectServer(uid);
            // 如果字典中还没有该服务器的条目，则添加一个新的ImSendEventArgs对象
            if (redata.ContainsKey(server) == false)
            {
                redata.Add(server, new ImSendEventArgs(server, senderClientId, message, receipt));
            }
            // 将当前接收者的ID添加到对应服务器的接收者列表中
            redata[server].ReceiveClientId.Add(uid);
        }
        // 将消息内容序列化为JSON字符串
        var messageJson = JsonConvert.SerializeObject(message);

        // 遍历字典中的每个服务器的消息发送参数
        foreach (var sendArgs in redata.Values)
        {
            // 触发OnSend事件，通知其他订阅者消息即将发送
            OnSend?.Invoke(this, sendArgs);

            // 使用Redis的发布功能将消息发送到指定的服务器
            // 构建Redis频道名称，格式为：_redisPrefix + "Server" + 服务器地址
            _redis.Publish($"{_redisPrefix}Server{sendArgs.Server}",
                // 将发送者ID、接收者ID列表、消息内容和是否需要确认的信息序列化为JSON字符串
                JsonConvert.SerializeObject((senderClientId, sendArgs.ReceiveClientId, messageJson, sendArgs.Receipt)));
        }
    }

    /// <summary>
    /// 事件订阅
    /// </summary>
    /// <param name="online">上线</param>
    /// <param name="offline">下线</param>
    public void EventBus(
        Action<(Guid clientId, string clientMetaData)> online,
        Action<(Guid clientId, string clientMetaData)> offline)
    {
        // 订阅上线事件
        _subscriber.Subscribe($"evt_{_redisPrefix}Online", (channel, msg) =>
        {
            var data = JsonConvert.DeserializeObject<(Guid clientId, string clientMetaData)>(msg);
            online(data);
        });

        // 订阅下线事件
        _subscriber.Subscribe($"evt_{_redisPrefix}Offline", (channel, msg) =>
        {
            var data = JsonConvert.DeserializeObject<(Guid clientId, string clientMetaData)>(msg);
            offline(data);
        });
    }

    /// <summary>
    /// 获取所在线客户端id
    /// </summary>
    /// <returns></returns>
    public IEnumerable<Guid> GetClientListByOnline()
    {
        // 获取哈希表中的所有键
        var keys = _redis.HashKeys($"{_redisPrefix}Online");

        // 转换和过滤
        return keys
            .Select(key => Guid.TryParse(key, out var tryguid) ? tryguid : Guid.Empty)
            .Where(guid => guid != Guid.Empty);
    }
}
